#include "file.h"

#define BITMAP_SIZE 0x1000
#define BITMAP_SECTOR_START 0x100
#define BITMAP_SECTOR_COUNT (BITMAP_SIZE >> 9)
#define FILE_ADDRESS_MAX 0x6A
#define ALLOCATE_SECTOR_START (BITMAP_SECTOR_START + BITMAP_SECTOR_COUNT)

static BYTE FsBitmap[BITMAP_SIZE];
static DWORD FsBitmapSize;
static struct INODE FsRootNode;
static struct SUPER_BLOCK FsSuperBlock;

static DWORD dwAllocateIndex;

static DWORD FsGetSectorCount(DWORD dwLength) {
  if (dwLength == 0) {
    return 0;
  }
  return ((dwLength - 1) >> 9) + 1;
}

void FsSuperBlockInitialize(void) {
  DevHardDiskRead(0, SUPER_BLOCK_LOCATION, 1, &FsSuperBlock);
  if (FsSuperBlock.dwMagic != SUPER_BLOCK_MAGIC) {
    FsSuperBlock.dwMagic = SUPER_BLOCK_MAGIC;
    FsSuperBlock.dwRoot = FsAllocateSector();
    {
      DWORD dwLow, dwHigh;
      dwLow = FsSuperBlock.dwRoot;
      dwHigh = BITMAP_SIZE << 3;
      while (dwLow < dwHigh) {
        DWORD dwMid = (dwLow + dwHigh) >> 1;
        if (DevHardDiskExisted(0, dwMid)) {
          dwLow = dwMid + 1;
        } else {
          dwHigh = dwMid;
        }
      }
      FsSuperBlock.dwSectors = dwLow;
    }
    {
      struct INODE tempNode;
      RtlZeroMemory(&tempNode, sizeof(struct INODE));
      RtlStringCopy(tempNode.lpcName, "root");
      tempNode.dwType = FILE_TYPE_FOLDER;
      tempNode.dwAttribute |= FILE_ATTR_KERNEL;
      tempNode.dwMagic = FILE_INODE_MAGIC;
      DevHardDiskWrite(0, FsSuperBlock.dwRoot, 1, &tempNode);
    }
    DevHardDiskWrite(0, SUPER_BLOCK_LOCATION, 1, &FsSuperBlock);
  }
}

void FsInitialize(void) {
  DevHardDiskRead(0, SUPER_BLOCK_LOCATION, 1, &FsSuperBlock);
  if (FsSuperBlock.dwMagic != SUPER_BLOCK_MAGIC) {
    RtlZeroMemory(FsBitmap, BITMAP_SIZE);
    DevHardDiskWrite(0, BITMAP_SECTOR_START, BITMAP_SECTOR_COUNT, FsBitmap);
    FsSuperBlockInitialize();
  } else {
    DevHardDiskRead(0, FsSuperBlock.dwRoot, 1, &FsRootNode);
    DevHardDiskRead(0, BITMAP_SECTOR_START, BITMAP_SECTOR_COUNT, FsBitmap);
  }
  FsBitmapSize = FsSuperBlock.dwSectors >> 5;
  if (FsBitmapSize > BITMAP_SIZE) {
    FsBitmapSize = BITMAP_SIZE;
  }
  dwAllocateIndex = 0;
}

DWORD FsGetRootNode(void) { return FsSuperBlock.dwRoot; }

DWORD FsAllocateSector(void) {
  register DWORD i = dwAllocateIndex;
  do {
    if (FsBitmap[i] != 0xFF) {
      register DWORD j;
      dwAllocateIndex = i;
      for (j = 0; j < 8; j++)
        if (((FsBitmap[i] >> j) & 1) == 0) {
          FsBitmap[i] |= (1 << j);
          DevHardDiskWrite(0, BITMAP_SECTOR_START + (i >> 9), 1,
                           FsBitmap + (i & 0xFFFFFE00));
          return ALLOCATE_SECTOR_START + (i << 3) + j;
        }
    }
    ++i;
    if (i >= FsBitmapSize)
      i = 0;
  } while (i != dwAllocateIndex);
  return NULL;
}

void FsAllocateSectors(BOOLEAN (*lpfnCallback)(DWORD_PTR)) {
  register DWORD i = dwAllocateIndex;
  do {
    if (FsBitmap[i] != 0xFF) {
      register DWORD j;
      for (j = 0; j < 8; j++)
        if (((FsBitmap[i] >> j) & 1) == 0) {
          FsBitmap[i] |= (1 << j);
          if (!lpfnCallback(ALLOCATE_SECTOR_START + (i << 3) + j)) {
            dwAllocateIndex = i;
            DevHardDiskWrite(0, BITMAP_SECTOR_START + (i >> 9), 1,
                             FsBitmap + (i & 0xFFFFFE00));
            return;
          }
        }
      DevHardDiskWrite(0, BITMAP_SECTOR_START + (i >> 9), 1,
                       FsBitmap + (i & 0xFFFFFE00));
    }
    ++i;
    if (i >= FsBitmapSize)
      i = 0;
  } while (i != dwAllocateIndex);
  lpfnCallback(NULL);
}

void FsFreeSector(DWORD dwAddress) {
  register DWORD dwIndex = (dwAddress - ALLOCATE_SECTOR_START) >> 3;
  FsBitmap[dwIndex] ^= (1 << (dwAddress & 0x7));
  DevHardDiskWrite(0, BITMAP_SECTOR_START + (dwIndex >> 9), 1,
                   FsBitmap + (dwIndex & 0xFFFFFE00));
}

DWORD FsCreateFile(DWORD dwBase, const LPSTR lpszFilename) {
  struct INODE Base;
  register DWORD i;
  register DWORD Sector;
  struct INODE FileNode;
  DevHardDiskRead(0, dwBase, 1, &Base);
  if (Base.dwMagic != FILE_INODE_MAGIC || Base.dwType != FILE_TYPE_FOLDER ||
      Base.dwLength >= FILE_ADDRESS_MAX) {
    return NULL;
  }
  for (i = 0; i < Base.dwLength; i++) {
    DevHardDiskRead(0, Base.dwAddress[i], 1, &FileNode);
    if (!RtlStringNCompare(lpszFilename, FileNode.lpcName, 0x40)) {
      return NULL;
    }
  }
  RtlZeroMemory(&FileNode, sizeof(struct INODE));
  RtlCopyMemory(FileNode.lpcName, lpszFilename, 0x40);
  FileNode.dwMagic = FILE_INODE_MAGIC;
  FileNode.dwType = FILE_TYPE_DEFAULT;
  FileNode.dwSuper = dwBase;
  Sector = FsAllocateSector();
  Base.dwAddress[Base.dwLength++] = Sector;
  DevHardDiskWrite(0, dwBase, 1, &Base);
  DevHardDiskWrite(0, Sector, 1, &FileNode);
  return Sector;
}

DWORD FsOpenFile(DWORD dwBase, const LPSTR lpszFilename) {
  struct INODE Base;
  register DWORD i;
  struct INODE FileNode;
  DevHardDiskRead(0, dwBase, 1, &Base);
  if (Base.dwMagic != FILE_INODE_MAGIC || Base.dwType != FILE_TYPE_FOLDER) {
    return NULL;
  }
  for (i = 0; i < Base.dwLength; i++) {
    DevHardDiskRead(0, Base.dwAddress[i], 1, &FileNode);
    if (!RtlStringNCompare(lpszFilename, FileNode.lpcName, 0x40)) {
      if (FileNode.dwType != FILE_TYPE_DEFAULT) {
        return NULL;
      }
      return Base.dwAddress[i];
    }
  }
  return NULL;
}

void FsDeleteFile(DWORD dwBase, const LPSTR lpszFilename) {
  struct INODE Base;
  register DWORD i;
  struct INODE FileNode;
  DevHardDiskRead(0, dwBase, 1, &Base);
  if (Base.dwMagic != FILE_INODE_MAGIC || Base.dwType != FILE_TYPE_FOLDER) {
    return;
  }
  for (i = 0; i < Base.dwLength; i++) {
    DevHardDiskRead(0, Base.dwAddress[i], 1, &FileNode);
    if (!RtlStringNCompare(lpszFilename, FileNode.lpcName, 0x40)) {
      if (FileNode.dwType != FILE_TYPE_DEFAULT) {
        return;
      }
      Base.dwAddress[i] = Base.dwAddress[--Base.dwLength];
      FsFreeSector(Base.dwAddress[i]);
      for (i = 0; i < FileNode.dwLength; i++) {
        FsFreeSector(FileNode.dwAddress[i]);
      }
      DevHardDiskWrite(0, dwBase, 1, &Base);
      return;
    }
  }
}

void FsReadFile(DWORD dwFile, DWORD dwStart, LPVOID lpvBuffer, DWORD dwLength,
                LPDWORD dwBytesOfRead) {
  struct INODE File;
  register DWORD i;
  BYTE bBuffer[0x200];
  DWORD dwEnd = dwStart + dwLength;
  DWORD dwStartAddr = dwStart >> 9;
  DWORD dwEndAddr;
  DWORD dwCur = dwStart;
  DevHardDiskRead(0, dwFile, 1, &File);
  if (File.dwMagic != FILE_INODE_MAGIC || File.dwType != FILE_TYPE_DEFAULT) {
    if (dwBytesOfRead)
      *dwBytesOfRead = 0;
    return;
  }
  if (dwStart >= File.dwLength) {
    if (dwBytesOfRead)
      *dwBytesOfRead = 0;
    return;
  }
  if (dwEnd > File.dwLength) {
    dwEnd = File.dwLength;
  }
  if (dwEnd == 0) {
    dwEnd = 1;
  }
  dwEndAddr = (dwEnd - 1) >> 9;
  for (i = dwStartAddr; i <= dwEndAddr; i++) {
    DevHardDiskRead(0, File.dwAddress[i], 1, bBuffer);
    DWORD dwNext = ((i + 1) << 9);
    if (dwNext > dwEnd) {
      dwNext = dwEnd;
    }
    RtlCopyMemory((LPBYTE)lpvBuffer + dwCur - dwStart,
                  bBuffer + dwCur - (i << 9), dwNext - dwCur);
    if (dwBytesOfRead)
      *dwBytesOfRead += dwNext - dwCur;
    dwCur = dwNext;
  }
}

void FsWriteFile(DWORD dwFile, DWORD dwStart, const LPVOID lpvBuffer,
                 DWORD dwLength, LPDWORD dwBytesOfWrite) {
  struct INODE File;
  register DWORD i;
  BYTE bBuffer[0x200];
  DWORD dwEnd = dwStart + dwLength;
  DWORD dwStartAddr = dwStart >> 9;
  DWORD dwEndAddr;
  DWORD dwCur = dwStart;
  DevHardDiskRead(0, dwFile, 1, &File);
  if (File.dwMagic != FILE_INODE_MAGIC || File.dwType != FILE_TYPE_DEFAULT) {
    if (dwBytesOfWrite)
      *dwBytesOfWrite = 0;
    return;
  }
  if (dwEnd > File.dwLength) {
    DWORD dwCnt1, dwCnt2;
    dwCnt1 = FsGetSectorCount(File.dwLength);
    if (dwEnd >= 0x6A * 0x200) {
      dwEnd = 0x6A * 0x200;
    }
    File.dwLength = dwEnd;
    dwCnt2 = FsGetSectorCount(dwEnd);
    for (; dwCnt1 < dwCnt2; dwCnt1++) {
      File.dwAddress[dwCnt1] = FsAllocateSector();
    }
  }
  if (dwEnd == 0) {
    dwEnd = 1;
  }
  dwEndAddr = (dwEnd - 1) >> 9;
  for (i = dwStartAddr; i <= dwEndAddr; i++) {
    DWORD dwNext = ((i + 1) << 9);
    if (dwNext > dwEnd) {
      dwNext = dwEnd;
    }
    if (dwNext - dwCur != 0x200) {
      DevHardDiskRead(0, File.dwAddress[i], 1, bBuffer);
    }
    RtlCopyMemory(bBuffer + dwCur - (i << 9),
                  (LPBYTE)lpvBuffer + dwCur - dwStart, dwNext - dwCur);
    DevHardDiskWrite(0, File.dwAddress[i], 1, bBuffer);
    if (dwBytesOfWrite)
      *dwBytesOfWrite += dwNext - dwCur;
    dwCur = dwNext;
  }
  DevHardDiskWrite(0, dwFile, 1, &File);
}

DWORD FsCreateDirectory(DWORD dwBase, const LPSTR lpszDirname) {
  struct INODE Base;
  register DWORD i;
  register DWORD Sector;
  struct INODE FileNode;
  DevHardDiskRead(0, dwBase, 1, &Base);
  if (Base.dwType != FILE_TYPE_FOLDER || Base.dwLength >= FILE_ADDRESS_MAX ||
      Base.dwMagic != FILE_INODE_MAGIC) {
    return NULL;
  }
  for (i = 0; i < Base.dwLength; i++) {
    DevHardDiskRead(0, Base.dwAddress[i], 1, &FileNode);
    if (!RtlStringNCompare(Base.lpcName, FileNode.lpcName, 0x40)) {
      return NULL;
    }
  }
  Sector = FsAllocateSector();
  Base.dwAddress[Base.dwLength++] = Sector;
  RtlZeroMemory(&FileNode, sizeof(struct INODE));
  RtlCopyMemory(FileNode.lpcName, lpszDirname, 0x40);
  FileNode.dwMagic = FILE_INODE_MAGIC;
  FileNode.dwType = FILE_TYPE_FOLDER;
  FileNode.dwSuper = dwBase;
  DevHardDiskWrite(0, Sector, 1, &FileNode);
  DevHardDiskWrite(0, dwBase, 1, &Base);
  return Sector;
}

DWORD FsOpenDirectory(DWORD dwBase, const LPSTR lpszDirname) {
  struct INODE Base;
  register DWORD i;
  struct INODE FileNode;
  DevHardDiskRead(0, dwBase, 1, &Base);
  if (Base.dwType != FILE_TYPE_FOLDER) {
    return NULL;
  }
  for (i = 0; i < Base.dwLength; i++) {
    DevHardDiskRead(0, Base.dwAddress[i], 1, &FileNode);
    if (!RtlStringNCompare(lpszDirname, FileNode.lpcName, 0x40)) {
      if (FileNode.dwType != FILE_TYPE_FOLDER) {
        return NULL;
      }
      return Base.dwAddress[i];
    }
  }
  return NULL;
}

void FsDeleteDirectory(DWORD dwBase, const LPSTR lpszDirname) {
  struct INODE Base;
  register DWORD i;
  struct INODE FileNode;
  DevHardDiskRead(0, dwBase, 1, &Base);
  if (Base.dwType != FILE_TYPE_FOLDER) {
    return;
  }
  for (i = 0; i < Base.dwLength; i++) {
    DevHardDiskRead(0, Base.dwAddress[i], 1, &FileNode);
    if (!RtlStringNCompare(lpszDirname, FileNode.lpcName, 0x40)) {
      if (FileNode.dwType != FILE_TYPE_FOLDER) {
        return;
      }
      Base.dwAddress[i] = Base.dwAddress[--Base.dwLength];
      FsFreeSector(Base.dwAddress[i]);
      for (i = 0; i < FileNode.dwLength; i++) {
        FsFreeSector(FileNode.dwAddress[i]);
      }
      DevHardDiskWrite(0, dwBase, 1, &Base);
      return;
    }
  }
}

void FsFindAllFiles(DWORD dwBase, void (*lpfnCallback)(DWORD)) {
  struct INODE Base;
  register DWORD i;
  DevHardDiskRead(0, dwBase, 1, &Base);
  if (Base.dwMagic != FILE_INODE_MAGIC || Base.dwType != FILE_TYPE_FOLDER) {
    return;
  }
  for (i = 0; i < Base.dwLength; i++) {
    lpfnCallback(Base.dwAddress[i]);
  }
  lpfnCallback(NULL);
}
